package org.skatiger.jrove.component.core;

import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;

import org.skatiger.jrove.JroveException;

public class FormatNumber extends FormatBase {
    private static final String NUMBER = "number";
    private static final String CURRENCY = "currency";
    private static final String PERCENT = "percent";
    private static final Class<?>[] GET_INSTANCE_PARAM_TYPES = new Class[] { String.class };

    private static Class<?> currencyClass;
    static {
        try {
            currencyClass = Class.forName("java.util.Currency");
            // container's runtime is J2SE 1.4 or greater
        } catch (Exception cnfe) {
        }
    }

    private String type;
    protected String currencyCode; // 'currencyCode' attribute
    protected String currencySymbol; // 'currencySymbol' attribute

    protected boolean isGroupingUsed; // 'groupingUsed' attribute
    protected boolean groupingUsedSpecified;
    protected int maxIntegerDigits; // 'maxIntegerDigits' attribute
    protected boolean maxIntegerDigitsSpecified;
    protected int minIntegerDigits; // 'minIntegerDigits' attribute
    protected boolean minIntegerDigitsSpecified;
    protected int maxFractionDigits; // 'maxFractionDigits' attribute
    protected boolean maxFractionDigitsSpecified;
    protected int minFractionDigits; // 'minFractionDigits' attribute
    protected boolean minFractionDigitsSpecified;

    public String getCurrencyCode() {
        return getPropertyValue("currencyCode", currencyCode);
    }

    public void setCurrencyCode(String currencyCode) {
        this.currencyCode = currencyCode;
    }

    public String getCurrencySymbol() {
        return getPropertyValue("currencySymbol", currencySymbol);
    }

    public void setCurrencySymbol(String currencySymbol) {
        this.currencySymbol = currencySymbol;
    }

    public boolean isGroupingUsed() {
        return getPropertyValue("isGroupingUsed", isGroupingUsed);
    }

    public void setGroupingUsed(boolean isGroupingUsed) {
        this.isGroupingUsed = isGroupingUsed;
    }

    public boolean isGroupingUsedSpecified() {
        return getPropertyValue("groupingUsedSpecified", groupingUsedSpecified);
    }

    public void setGroupingUsedSpecified(boolean groupingUsedSpecified) {
        this.groupingUsedSpecified = groupingUsedSpecified;
    }

    public int getMaxIntegerDigits() {
        return getPropertyValue("maxIntegerDigits", maxIntegerDigits);
    }

    public void setMaxIntegerDigits(int maxIntegerDigits) {
        this.maxIntegerDigits = maxIntegerDigits;
    }

    public boolean isMaxIntegerDigitsSpecified() {
        return getPropertyValue("maxIntegerDigitsSpecified",
                maxIntegerDigitsSpecified);
    }

    public void setMaxIntegerDigitsSpecified(boolean maxIntegerDigitsSpecified) {
        this.maxIntegerDigitsSpecified = maxIntegerDigitsSpecified;
    }

    public int getMinIntegerDigits() {
        return getPropertyValue("minIntegerDigits", minIntegerDigits);
    }

    public void setMinIntegerDigits(int minIntegerDigits) {
        this.minIntegerDigits = minIntegerDigits;
    }

    public boolean isMinIntegerDigitsSpecified() {
        return getPropertyValue("minIntegerDigitsSpecified",
                minIntegerDigitsSpecified);
    }

    public void setMinIntegerDigitsSpecified(boolean minIntegerDigitsSpecified) {
        this.minIntegerDigitsSpecified = minIntegerDigitsSpecified;
    }

    public int getMaxFractionDigits() {
        return getPropertyValue("maxFractionDigits", maxFractionDigits);
    }

    public void setMaxFractionDigits(int maxFractionDigits) {
        this.maxFractionDigits = maxFractionDigits;
    }

    public boolean isMaxFractionDigitsSpecified() {
        return getPropertyValue("maxFractionDigitsSpecified",
                maxFractionDigitsSpecified);
    }

    public void setMaxFractionDigitsSpecified(boolean maxFractionDigitsSpecified) {
        this.maxFractionDigitsSpecified = maxFractionDigitsSpecified;
    }

    public int getMinFractionDigits() {
        return getPropertyValue("minFractionDigits", minFractionDigits);
    }

    public void setMinFractionDigits(int minFractionDigits) {
        this.minFractionDigits = minFractionDigits;
    }

    public boolean isMinFractionDigitsSpecified() {
        return getPropertyValue("minFractionDigitsSpecified",
                minFractionDigitsSpecified);
    }

    public void setMinFractionDigitsSpecified(boolean minFractionDigitsSpecified) {
        this.minFractionDigitsSpecified = minFractionDigitsSpecified;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getType() {
        return getPropertyValue("type", type);
    }

    @Override
    protected boolean isSupport(Class<?> clazz) {
        return Number.class.isAssignableFrom(clazz)
                || String.class.equals(clazz);
    }

    private NumberFormat createFormatter(Locale loc) {
        NumberFormat formatter = null;
        String type = getType();
        if ((type == null) || NUMBER.equalsIgnoreCase(type)) {
            formatter = NumberFormat.getNumberInstance(loc);
        } else if (CURRENCY.equalsIgnoreCase(type)) {
            formatter = NumberFormat.getCurrencyInstance(loc);
        } else if (PERCENT.equalsIgnoreCase(type)) {
            formatter = NumberFormat.getPercentInstance(loc);
        } else {
            throw new JroveException("FORMAT_NUMBER_INVALID_TYPE " + type);
        }

        return formatter;
    }

    @Override
    protected String getFormatedValue() {
        Number input;
        String formatted = null;
        Object _value = getValue();
        if (String.class.equals(_value.getClass())) {
            if (((String) _value).indexOf('.') != -1) {
                input = Double.valueOf((String) _value);
            } else {
                input = Long.valueOf((String) _value);
            }
        } else {
            input = (Number) _value;
        }

        Locale loc = getLocale();
        if (loc != null) {
            // Create formatter 
            NumberFormat formatter = null;
            String pattern = getPattern();
            if ((pattern != null) && !pattern.equals("")) {
                // if 'pattern' is specified, 'type' is ignored
                DecimalFormatSymbols symbols = new DecimalFormatSymbols(loc);
                formatter = new DecimalFormat(pattern, symbols);
            } else {
                formatter = createFormatter(loc);
            }
            if (((pattern != null) && !pattern.equals(""))
                    || CURRENCY.equalsIgnoreCase(type)) {
                try {
                    setCurrency(formatter);
                } catch (Exception e) {
                    throw new JroveException("FORMAT_NUMBER_CURRENCY_ERROR", e);
                }
            }
            configureFormatter(formatter);
            formatted = formatter.format(input);
        } else {
            // no formatting locale available, use toString()
            formatted = input.toString();
        }
        return formatted;
    }

    private void setCurrency(NumberFormat formatter) throws Exception {
        String code = null;
        String symbol = null;
        String currencyCode = getCurrencyCode();
        String currencySymbol = getCurrencySymbol();
        if ((currencyCode == null) && (currencySymbol == null)) {
            return;
        }

        if ((currencyCode != null) && (currencySymbol != null)) {
            if (currencyClass != null) {
                code = currencyCode;
            } else {
                symbol = currencySymbol;
            }
        } else if (currencyCode == null) {
            symbol = currencySymbol;
        } else {
            if (currencyClass != null) {
                code = currencyCode;
            } else {
                symbol = currencyCode;
            }
        }

        if (code != null) {
            Object[] methodArgs = new Object[1];

            /*
             * java.util.Currency.getInstance()
             */
            Method m = currencyClass.getMethod("getInstance",
                    GET_INSTANCE_PARAM_TYPES);
            methodArgs[0] = code;
            Object currency = m.invoke(null, methodArgs);
            /*
             * java.text.NumberFormat.setCurrency()
             */
            Class<?>[] paramTypes = new Class[1];
            paramTypes[0] = currencyClass;
            Class<?> numberFormatClass = Class
                    .forName("java.text.NumberFormat");
            m = numberFormatClass.getMethod("setCurrency", paramTypes);
            methodArgs[0] = currency;
            m.invoke(formatter, methodArgs);
        } else {
            /*
             * Let potential ClassCastException propagate up (will almost
             * never happen)
             */
            DecimalFormat df = (DecimalFormat) formatter;
            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
            dfs.setCurrencySymbol(symbol);
            df.setDecimalFormatSymbols(dfs);
        }
    }

    /*
     * Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits',
     * 'maxFractionDigits', and 'minFractionDigits' attributes to the given
     * formatter.
     */
    private void configureFormatter(NumberFormat formatter) {
        if (groupingUsedSpecified)
            formatter.setGroupingUsed(isGroupingUsed);
        if (maxIntegerDigitsSpecified)
            formatter.setMaximumIntegerDigits(maxIntegerDigits);
        if (minIntegerDigitsSpecified)
            formatter.setMinimumIntegerDigits(minIntegerDigits);
        if (maxFractionDigitsSpecified)
            formatter.setMaximumFractionDigits(maxFractionDigits);
        if (minFractionDigitsSpecified)
            formatter.setMinimumFractionDigits(minFractionDigits);
    }

}
